package edu.lambdateacher.expressions;

/*
 LambdaTeacher
 Copyright (C) 2000  Kim Mason
 Copyright (C) 2014  Vitalij Zadneprovskij

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

import java.util.HashSet;
import java.util.Set;

/**
 * The class Binding isn't really a Lambda Expression as such, but just denotes
 * the attachment of a name to a Lambda Expression. The parentmodified in
 * Expression is used when a bindings child is modified, and thus the binding
 * must be trashed. Binding is a subclass of Expression, because a Binding may
 * wish to trash its parent bindings.
 */
public class Binding extends Expression implements Cloneable {
	private String name;
	private Expression expr;

	/**
	 * A simple constructor function, which sets everything to null.
	 */
	public Binding() {
		super(); // implicit
		setName(null);
		setExpr(null);
	}

	public Binding(String name, Expression expr) {
		super();
		this.setName(name);
		this.setExpr(expr);
	}

	public final String getName() {
		return this.name;
	}

	public final void setName(String inname) {
		this.name = inname;
	}

	public final Expression getExpr() {
		return this.expr;
	}

	public final void setExpr(Expression inexpr) {
		this.expr = inexpr;
		if (inexpr != null){
			if (inexpr.getParentModified()){
				this.setParentModified(true);
			}
		}
	}

	/**
	 * clone simply returns a new binding which is a copy of the previous one.
	 * Its name and body are copies too.
	 */
	public Object clone() {
		Binding toreturn = (Binding) super.clone();
		String thename = null;
		Expression theexpr = null;

		thename = new String(this.getName());
		if (getExpr() != null){
			theexpr = (Expression) this.getExpr().clone();
		}
		setName(thename);
		setExpr(theexpr);
		return toreturn;
	}

	/**
	 * unParse() simply un-parses the current binding
	 */
	public String stringRepresentation(boolean verbose) {
		return getName() + " ";
		// return getExpr().unParse();
	}

	/**
	 * containsUnbound returns true if the bindings expression contains inname
	 * as an unbound variable.
	 */
	public boolean containsUnbound(String inname) {
		if (this.getExpr() == null){
			return false;
		}

		return this.getExpr().containsUnbound(inname);

	}

	/**
	 * normal_reduce attempts to reduce the expression withing this binding. If
	 * parentmodified in the returned expression is true, the current binding is
	 * trashed.
	 */
	public Expression normalReduce() {
		Expression oldbody = this.getExpr();

		// if we can't reduce the body, return a clone.
		if (oldbody == null){
			return this;
		}

		// reduce the body
		Expression newbody = oldbody.normalReduce();

		// if newbody is null, trash the current binding
		if (newbody == null){
			return newbody;
		}

		// if the body's parentmodified is true, we must ditch the current
		// binding
		if (newbody.getParentModified() == true){
			return newbody;
		}

		// return the binding with the new body.
		Expression toreturn = this;
		((Binding) toreturn).setExpr(newbody);

		return toreturn;
	}

	/*
	 * reduceExpression simply searches for inexpr, and if the current
	 * expression is equal to inexpr, it reduces it and returns the result.
	 */
	public Expression reduceExpression(Expression inexpr) {
		// if we are trying to reduce the binding, we can't
		Expression toreturn = this;

		if (this == inexpr) {
			if (this.getExpr() == null) {
				
			} else {
				toreturn = this.getExpr().reduceExpression(this.getExpr());
			}

			return toreturn;
		}

		// try and reduce within the bindings child
		Expression oldbody = this.getExpr();

		// if we can't reduce the body, return this
		if (oldbody == null){
			return this;
		}

		// reduce the body
		Expression newbody = oldbody.reduceExpression(inexpr);

		// if newbody is null, trash the current binding
		if (newbody == null){
			return newbody;
		}

		// if the body's parentmodified is true, we must ditch the current
		// binding
		if (newbody.getParentModified() == true){
			return newbody;
		}

		// return the binding with the new body.
		((Binding) toreturn).setExpr(newbody);
		return toreturn;
	}

	/**
	 * name_substitute attempts to sub a clone of param into any variable that
	 * matches inname in this bindings expression.
	 */
	public Expression nameSubstitute(String inname, Expression param) {
		if (inname == null){
			return this;
		}

		Expression newbody = null;
		Expression oldbody = this.getExpr();

		if (oldbody == null){
			return this;
		}

		newbody = oldbody.nameSubstitute(inname, param);

		// if this happens, something is wrong.
		if (newbody == null){
			return null;
		}

		// if the binding is modified, ditch it and return the sub-expr
		if (newbody.getParentModified() == true){
			return newbody;
		}

		// return a new binding, with the new body.
		Expression toreturn = this;
		this.setExpr(newbody);
		return toreturn;
	}

	/**
	 * containsReducable returns true if the current binding contains a redex.
	 * Otherwise, it returns false.
	 */
	public boolean containsReducable() {
		Expression theexpr = this.getExpr();
		// current expression is null, and so can't contain a redex.
		if (theexpr == null){
			return false;
		}

		return theexpr.containsReducable();
	}

	/**
	 * findNormalReducable simply returns any normal reducable sub expression of
	 * this Binding (not a clone).
	 */
	public Expression findNormalReducable() {
		if (this.getExpr() == null){
			return null;
		}

		return this.getExpr().findNormalReducable();
	}

	/**
	 * findApplicativeReducable attempts to find the applicative reducable
	 * subexpression of the current Expression
	 */
	public Expression findApplicativeReducable() {
		if (this.getExpr() == null){
			return null;
		}

		return this.getExpr().findApplicativeReducable();
	}

	/**
	 * findUnboundVars returns a HashSet with the names of all of the unbound
	 * variables in the current expression. The input HashSet (bound_vars)
	 * contains all of the variable names that are bound.
	 */
	public Set findUnboundVars(Set bound_vars) {
		Set toreturn = new HashSet();

		if (this.getExpr() != null){
			toreturn = this.getExpr().findUnboundVars(bound_vars);
		}

		return toreturn;
	}

	/*
	 * removeAllBindings walks down the expression tree, and removes any
	 * bindings that it finds. This is a binding, so trash it.
	 */
	public Expression removeAllBindings() {
		return this.getExpr().removeAllBindings();
	}

	/*
	 * removeBinding walks down the expression tree, and removes any occurences
	 * of the input parameter (toremove).
	 */
	public Expression removeBinding(Binding toremove) {
		if (this == toremove){
			return this.getExpr();
		}else{
			return this;
		}
	}

}
